
/* GCSx
** COLORSELECT.CPP
**
** Color selection toolbar
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

Uint8 ColorSelect::defaultColors[NUM_COLORS][4] = {
    { 0, 0, 0, 0 },
    { 128, 128, 128, 255 }, { 192, 192, 192, 255 }, { 255, 255, 255, 255 },
    { 255, 0, 0, 255 }, { 255, 128, 0, 255 }, { 255, 255, 0, 255 },
    { 0, 255, 0, 255 }, { 0, 255, 255, 255 },
    { 0, 0, 255, 255 }, { 255, 0, 255, 255 }
};

int ColorSelect::defaultSelected[2] = { 3, 0 };

const string ColorSelect::wtArrowUp("m");
const string ColorSelect::wtArrowDown("n");

int ColorSelect::arrowHeight = 0;
int ColorSelect::arrowXOffset = 0;

ColorSelect::ColorSelect(ColorStore* cStorage, int defaultTransparent, int allowBk, int alphaBitDepth, int myBitDepth) : Window() { start_func
    assert((myBitDepth <= 8) && (myBitDepth >= 3));

    myFrame = NULL;
    storage = cStorage;
    numColors = allowBk ? 2 : 1;
    bitDepth = myBitDepth;
    bitDepthAlpha = alphaBitDepth;
    maxComponent = (1 << myBitDepth) - 1;
    maxAlpha = (1 << alphaBitDepth) - 1;

    // Default colors
    memcpy(color, defaultColors, NUM_COLORS * 4);

    // First color is black or transparent black dep. on tileset mode
    color[0][3] = defaultTransparent ? 0 : 255;

    // Scale default colors to depth
    for (int pos = 0; pos < NUM_COLORS; ++pos) {
        for (int sub = 0; sub < 3; ++sub) {
            color[pos][sub] = scaleComponent(color[pos][sub], 255, maxComponent);
        }
        color[pos][3] = scaleComponent(color[pos][3], 255, maxAlpha);
    }

    selected[SELECTED_FG] = defaultSelected[SELECTED_FG];
    selectedFirst[SELECTED_FG] = defaultSelected[SELECTED_FG];
    selectedLast[SELECTED_FG] = defaultSelected[SELECTED_FG];
    if (numColors == 2) {
        selected[SELECTED_BK] = defaultSelected[SELECTED_BK];
        selectedFirst[SELECTED_BK] = defaultSelected[SELECTED_BK];
        selectedLast[SELECTED_BK] = defaultSelected[SELECTED_BK];
    }
    else {
        // Color 0 can't change, so put unused BK there
        selected[SELECTED_BK] = 0;
        selectedFirst[SELECTED_BK] = 0;
        selectedLast[SELECTED_BK] = 0;
    }

    // Marker sizing
    arrowHeight = fontHeight(FONT_WIDGET);
    arrowXOffset = (COLOR_SIZE - fontWidth(wtArrowUp, FONT_WIDGET)) / 2;
    
    // Fill storage
    apply();

    // Calculate size
    resize(NUM_COLORS * (COLOR_SIZE + COLOR_SEPARATION) - COLOR_SEPARATION,
           arrowHeight * numColors + COLOR_HEIGHT);
}

FrameWindow* ColorSelect::createWindowed() { start_func
    // Prevent duplication
    if (myFrame) {
        return myFrame;
    }

    // We remember the frame pointer even though it'll delete itself
    myFrame = new FrameWindow("Colors", FrameWindow::RESIZING_SNAP, FrameWindow::FRAMETYPE_DIALOG, this, FrameWindow::TITLEBAR_TOOL, 0);
    return myFrame;
}

void ColorSelect::changeDefaultTransparent(int defaultTransparent) { start_func
    color[0][3] = defaultTransparent ? 0 : 255;
    setDirty();
    apply();
}

int ColorSelect::addColor(int which, Uint8 r, Uint8 g, Uint8 b, Uint8 a, int putInCurrent) { start_func
    int slot = selected[which];

    // (never ever modify slot 0)
    if ((!putInCurrent) || (slot == 0)) {
        // If color exists in current palette, select it
        for (int pos = 0; pos < NUM_COLORS; ++pos) {
            if ((color[pos][0] == r) && (color[pos][1] == g) &&
                (color[pos][2] == b) && (color[pos][3] == a)) {
                return colorSelection(which, pos);
            }
        }

        // Put color in next available slot after current selection.
        // Skip slots selected by either fg or bk, and first slot.
        // If no available slots, stick in current slot.
        while (((slot >= selectedFirst[SELECTED_FG]) && (slot <= selectedLast[SELECTED_FG])) ||
               ((slot >= selectedFirst[SELECTED_BK]) && (slot <= selectedLast[SELECTED_BK])) ||
               (slot == 0)) {
            if (++slot >= NUM_COLORS) slot = 0;
            if (slot == selected[which]) break;
        }

        // (never ever modify slot 0)
        if (slot == 0) slot = 1;
    }

    color[slot][0] = r;
    color[slot][1] = g;
    color[slot][2] = b;
    color[slot][3] = a;
    setDirty();
    apply();

    return colorSelection(which, slot);
}

void ColorSelect::editColor(int pos) { start_func
    assert((pos >= 0) && (pos < NUM_COLORS));

    // Can't edit background color
    if (pos == 0) return;

    if (RGBSelect::create()->run(color[pos][0], color[pos][1], color[pos][2], color[pos][3], bitDepthAlpha, bitDepth)) {
        setDirty();
        apply();
    }
}

int ColorSelect::colorSelection(int which) const { start_func
    return selected[which];
}

int ColorSelect::colorSelection(int which, int newPos, int drag) { start_func
    // Wraparound
    if (newPos < 0) newPos = NUM_COLORS - 1;
    if (newPos >= NUM_COLORS) newPos = 0;

    // Drag?
    if (drag) {
        // If selection beginning matches insert point
        if (selectedFirst[which] == selected[which]) {
            // Drag beginning point
            selectedFirst[which] = selected[which] = newPos;
        }
        else {
            // Drag end point
            selectedLast[which] = selected[which] = newPos;
        }


        // Ensure in proper order
        if (selectedLast[which] < selectedFirst[which]) {
            swap(selectedFirst[which], selectedLast[which]);
        }
    }
    else {
        // Insertion point and selection
        selectedFirst[which] = selectedLast[which] = selected[which] = newPos;
    }

    // Also update default
    defaultSelected[which] = selected[which];

    // Dirty
    setDirty();
    apply();

    return selected[which];
}

int ColorSelect::event(int hasFocus, const SDL_Event* event) { start_func
    assert(event);
    assert(parent);

    int selColor;
    int drag;

    switch (event->type) {
        case SDL_MOUSEBUTTONDOWN:
        case SDL_MOUSEBUTTONDBL:
            selColor = event->button.x / (COLOR_SIZE + COLOR_SEPARATION);

            if (((event->button.button == SDL_BUTTON_LEFT) || (event->button.button == SDL_BUTTON_RIGHT)) &&
                (selColor >= 0) && (selColor < NUM_COLORS)) {
                colorSelection((event->button.button == SDL_BUTTON_LEFT) || (numColors == 1) ? SELECTED_FG : SELECTED_BK, selColor, SDL_GetModState() & KMOD_SHIFT);
                if (event->type == SDL_MOUSEBUTTONDBL) editColor(selColor);
                return 1;
            }
            break;

        case SDL_MOUSEMOTION:
            if ((event->motion.state & SDL_BUTTON_LMASK) || (event->motion.state & SDL_BUTTON_RMASK)) {
                selColor = ((Sint16)event->button.x) / (COLOR_SIZE + COLOR_SEPARATION);

                if ((selColor >= 0) && (selColor < NUM_COLORS) && (event->button.y < height)) {
                    colorSelection((event->motion.state & SDL_BUTTON_LMASK) || (numColors == 1) ? SELECTED_FG : SELECTED_BK, selColor, 1);
                    return 1;
                }
            }
            break;

        case SDL_COMMAND:
            switch (event->user.code) {
                case EDIT_GRADIENT:
                    // Only perform and dirty if appropriate...
                    if (selectedLast[SELECTED_FG] > selectedFirst[SELECTED_FG]) {
                        int last = selectedLast[SELECTED_FG];
                        int first = selectedFirst[SELECTED_FG];
                        // Number of steps
                        int count = last - first;
                        for (int pos = first + 1; pos < last; ++pos) {
                            for (int sub = 0; sub < 4; ++sub) {
                                color[pos][sub] = color[first][sub] + (color[last][sub] - color[first][sub]) * (pos - first) / count;
                            }
                        }
                        setDirty();
                        apply();
                    }
                    // ... but we always use the command
                    return 1;

                case EDIT_COPY:
                    clipboardCopy(&color[selectedFirst[SELECTED_FG]][0], selectedLast[SELECTED_FG] - selectedFirst[SELECTED_FG] + 1);
                    return 1;

                case EDIT_PASTE:
                    if ((selectedFirst[SELECTED_FG] > 0) && (canConvertClipboard(CLIPBOARD_RGBA))) {
                        if (selectedFirst[SELECTED_FG] == selectedLast[SELECTED_FG]) {
                            // Paste as much as possible
                            clipboardPasteColor(&color[selectedFirst[SELECTED_FG]][0], NUM_COLORS - selectedFirst[SELECTED_FG]);
                        }
                        else {
                            // Fill selection
                            int count = selectedLast[SELECTED_FG] - selectedFirst[SELECTED_FG] + 1;
                            int offset = 0;
                            while (count) {
                                int did = clipboardPasteColor(&color[selectedFirst[SELECTED_FG] + offset][0], count);
                                count -= did;
                                offset += did;
                            }
                        }
                        setDirty();
                        apply();
                        return 1;
                    }
                    break;
            }
            break;

        case SDL_KEYDOWN:
            selColor = (event->key.keysym.mod & KMOD_CTRL) && (numColors == 2) ? SELECTED_BK : SELECTED_FG;
            drag = (event->key.keysym.mod & KMOD_SHIFT) ? 1 : 0;

            switch (combineKey(event->key.keysym.sym, event->key.keysym.mod & ~(KMOD_SHIFT | KMOD_CTRL))) {
                case SDLK_RIGHT:
                case SDLK_DOWN:
                    colorSelection(selColor, selected[selColor] + 1, drag);
                    return 1;

                case SDLK_LEFT:
                case SDLK_UP:
                    colorSelection(selColor, selected[selColor] - 1, drag);
                    return 1;

                case SDLK_HOME:
                    colorSelection(selColor, 0, drag);
                    return 1;

                case SDLK_END:
                    colorSelection(selColor, NUM_COLORS - 1, drag);
                    return 1;

                case SDLK_KP_ENTER:
                case SDLK_RETURN:
                    editColor(selected[selColor]);
                    return 1;
            }
    }

    return 0;
}

void ColorSelect::apply() { start_func
    // Currently, our parent never knows the selected ranges, but those aren't used yet
    storage->fg.r = color[selected[SELECTED_FG]][0];
    storage->fg.g = color[selected[SELECTED_FG]][1];
    storage->fg.b = color[selected[SELECTED_FG]][2];
    storage->fg.a = color[selected[SELECTED_FG]][3];
    storage->bk.r = color[selected[SELECTED_BK]][0];
    storage->bk.g = color[selected[SELECTED_BK]][1];
    storage->bk.b = color[selected[SELECTED_BK]][2];
    storage->bk.a = color[selected[SELECTED_BK]][3];

    // Remember defaults
    memcpy(defaultColors, color, NUM_COLORS * 4);
    // Scale default colors to depth
    for (int pos = 0; pos < NUM_COLORS; ++pos) {
        assert(maxComponent);
        for (int sub = 0; sub < 3; ++sub) {
            defaultColors[pos][sub] = scaleComponent(defaultColors[pos][sub], maxComponent, 255);
        }
        if (maxAlpha) {
            defaultColors[pos][3] = scaleComponent(defaultColors[pos][3], maxAlpha, 255);
        }
        else {
            defaultColors[pos][3] = 255;
        }
    }
    defaultSelected[SELECTED_FG] = selected[SELECTED_FG];
    if (numColors == 2) defaultSelected[SELECTED_BK] = selected[SELECTED_BK];
    
    // Inform parent
    if (parent) parent->childModified(this);
}

void ColorSelect::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
    assert(destSurface);

    if (visible) {
        // If dirty, redraw all
        if (dirty) {
            getRect(toDisplay);
            toDisplay.x += xOffset;
            toDisplay.y += yOffset;
            dirty = 0;
            intersectRects(toDisplay, clipArea);
        }

        // Anything to draw?
        if (toDisplay.w) {
            SDL_SetClipRect(destSurface, &toDisplay);

            xOffset += x;
            yOffset += y;

            // This widget only redraws colors that are part of the dirty area
            SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_FILL]);

            // Selection markers
            if (selectedFirst[SELECTED_FG] != selectedLast[SELECTED_FG]) {
                int xBox = xOffset + (COLOR_SIZE + COLOR_SEPARATION) * selectedFirst[SELECTED_FG];
                int wBox = (COLOR_SIZE + COLOR_SEPARATION) * (selectedLast[SELECTED_FG] - selectedFirst[SELECTED_FG] + 1) - COLOR_SEPARATION;
                drawGuiBox(xBox, yOffset, wBox, arrowHeight, 1, destSurface);
                drawGradient(xBox + 1, yOffset + 1, wBox - 2, arrowHeight - 2, guiRGB[COLOR_SELECTION1], guiRGB[COLOR_SELECTION2], destSurface);
            }
            if ((numColors == 2) && (selectedFirst[SELECTED_BK] != selectedLast[SELECTED_BK])) {
                int xBox = xOffset + (COLOR_SIZE + COLOR_SEPARATION) * selectedFirst[SELECTED_BK];
                int wBox = (COLOR_SIZE + COLOR_SEPARATION) * (selectedLast[SELECTED_BK] - selectedFirst[SELECTED_BK] + 1) - COLOR_SEPARATION;
                drawGuiBox(xBox, arrowHeight + COLOR_HEIGHT + yOffset, wBox, arrowHeight, 1, destSurface);
                drawGradient(xBox + 1, arrowHeight + COLOR_HEIGHT + yOffset + 1, wBox - 2, arrowHeight - 2, guiRGB[COLOR_SELECTION1], guiRGB[COLOR_SELECTION2], destSurface);
            }

            int last = (toDisplay.x + toDisplay.w - xOffset) / (COLOR_SIZE + COLOR_SEPARATION);
            for (int pos = (toDisplay.x - xOffset) / (COLOR_SIZE + COLOR_SEPARATION); pos <= last; ++pos) {
                int xPos = xOffset + (COLOR_SIZE + COLOR_SEPARATION) * pos;

                // 0 alpha shows different
                if ((color[pos][3] == 0) && (maxAlpha)) {
                    drawRect(xPos + COLOR_BEVEL, arrowHeight + yOffset + COLOR_BEVEL, COLOR_SIZE - COLOR_BEVEL * 2, COLOR_HEIGHT - COLOR_BEVEL * 2,
                             guiPacked[COLOR_TRANSPARENT1], destSurface);
                    drawLine(xPos + COLOR_BEVEL, arrowHeight + yOffset + COLOR_BEVEL, xPos + COLOR_SIZE - COLOR_BEVEL - 1, arrowHeight + yOffset + COLOR_HEIGHT - COLOR_BEVEL - 1,
                             guiPacked[COLOR_TRANSPARENT2], destSurface);
                    drawLine(xPos + COLOR_BEVEL, arrowHeight + yOffset + COLOR_HEIGHT - COLOR_BEVEL - 1, xPos + COLOR_SIZE - COLOR_BEVEL - 1, arrowHeight + yOffset + COLOR_BEVEL,
                             guiPacked[COLOR_TRANSPARENT2], destSurface);
                }
                else {
                    assert(maxComponent);
                    drawRect(xPos + COLOR_BEVEL, arrowHeight + yOffset + COLOR_BEVEL, COLOR_SIZE - COLOR_BEVEL * 2, COLOR_HEIGHT - COLOR_BEVEL * 2,
                             SDL_MapRGB(destSurface->format,
                                        scaleComponent(color[pos][0], maxComponent, 255),
                                        scaleComponent(color[pos][1], maxComponent, 255),
                                        scaleComponent(color[pos][2], maxComponent, 255)), destSurface);

                    // < 255 alpha shows shaded part on lower half
                    if ((color[pos][3] < maxAlpha) && (maxAlpha)) {
                        drawRect(xPos + COLOR_BEVEL, arrowHeight + yOffset + COLOR_HEIGHT / 2, COLOR_SIZE - COLOR_BEVEL * 2, COLOR_HEIGHT - COLOR_HEIGHT / 2 - COLOR_BEVEL,
                                 SDL_MapRGB(destSurface->format,
                                            scaleComponent(color[pos][0] * color[pos][3] >> bitDepthAlpha, maxComponent, 255),
                                            scaleComponent(color[pos][1] * color[pos][3] >> bitDepthAlpha, maxComponent, 255),
                                            scaleComponent(color[pos][2] * color[pos][3] >> bitDepthAlpha, maxComponent, 255)),
                                            destSurface);
                    }
                }
                drawGuiBoxInvert(xPos, arrowHeight + yOffset,
                                 COLOR_SIZE, COLOR_HEIGHT, COLOR_BEVEL, destSurface);

                // Selected?
                if (pos == selected[SELECTED_FG]) {
                    drawText(wtArrowDown, guiRGB[selectedFirst[SELECTED_FG] == selectedLast[SELECTED_FG] ? COLOR_TEXT : COLOR_TEXTBOX], xPos + arrowXOffset, yOffset, destSurface, FONT_WIDGET);
                }
                if ((numColors == 2) && (pos == selected[SELECTED_BK])) {
                    drawText(wtArrowUp, guiRGB[selectedFirst[SELECTED_BK] == selectedLast[SELECTED_BK] ? COLOR_TEXT : COLOR_TEXTBOX], xPos + arrowXOffset, arrowHeight + COLOR_HEIGHT + yOffset, destSurface, FONT_WIDGET);
                }
            }
        }
    }
}

const char* ColorSelect::tooltip(int xPos, int yPos) const { start_func
    // @TODO: more specific tool tips?
    return "Select color; double-click to edit";
}

void ColorSelect::alphaDepth(int newAlphaDepth) { start_func
    bitDepthAlpha = newAlphaDepth;
    int oldMaxAlpha = maxAlpha;
    maxAlpha = (1 << bitDepthAlpha) - 1;

    // Scale to new depth
    for (int pos = 0; pos < NUM_COLORS; ++pos) {
        if ((oldMaxAlpha) && (maxAlpha)) {
            color[pos][3] = scaleComponent(color[pos][3], oldMaxAlpha, maxAlpha);
        }
        else {
            color[pos][3] = maxAlpha;
        }
    }

    setDirty();
    apply();
}

Window::CommandSupport ColorSelect::supportsCommand(int code) const { start_func
    switch (code) {
        case EDIT_GRADIENT:
            if (selectedFirst[SELECTED_FG] < selectedLast[SELECTED_FG] - 1) return Window::COMMAND_ENABLE;
            return Window::COMMAND_DISABLE;

        case EDIT_COPY:
            return Window::COMMAND_ENABLE;

        case EDIT_PASTE:
            if (selected[SELECTED_FG] == 0) return Window::COMMAND_DISABLE;
            if (canConvertClipboard(CLIPBOARD_RGBA)) return Window::COMMAND_ENABLE;
            return Window::COMMAND_DISABLE;
    }

    return Window::COMMAND_HIDE;
}


